﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using SysImaging = System.Windows.Media.Imaging;
using iNet.Media.Imaging.Exif;
using iNet.Media.Xmp;
using iNet.Tasks;

namespace iNet.Media.Imaging
{
	/// <summary>
	/// TIFF 影像編碼工作。
	/// </summary>
	public class TiffImageEncodeTask : BasicImageEncodeTask
	{
		#region 建構子
		/// <summary>
		/// 初始化新的 TiffImageEncodeTask 執行個體。
		/// </summary>
		public TiffImageEncodeTask()
			: base(ImageFormats.Tiff)
		{ }
		/// <summary>
		/// 初始化新的 TiffImageEncodeTask 執行個體。
		/// </summary>
		/// <param name="destination">儲存編碼結果的媒體儲存。</param>
		public TiffImageEncodeTask(MediaStorage destination)
			: this()
		{
			this.Destination = destination;
		}
		#endregion
		#region CreateCompatibleMetadata
		/// <summary>
		/// 建立與此媒體格式相容且可編輯的中繼資料。
		/// </summary>
		/// <param name="template">現有的中繼資料。</param>
		/// <returns>與此媒體格式相容的中繼資料。</returns>
		public override IMetadata CreateCompatibleMetadata(IMetadata template)
		{
			TiffMetadata metadata = (template as TiffMetadata);
			if (metadata == null || metadata.IsReadOnly)
				return new TiffMetadata(template, false);
			return metadata;
		}
		#endregion
		#region CreateEncoder
		internal override SysImaging.BitmapEncoder CreateEncoder()
		{
			return new SysImaging.TiffBitmapEncoder();
		}
		#endregion
		#region Encode
		protected override bool Encode(TaskVersion version, MediaStorage destination, MediaEncodeRequests mediaEncodeReqs, ImageEncodeRequests imageEncodeReqs)
		{
			//編碼影像
			if (!base.Encode(version, destination, mediaEncodeReqs, imageEncodeReqs))
				return false;

			//中斷確認
			if (this.IsCanceling)
				return true;

			//編碼中繼資料
			if ((mediaEncodeReqs & Media.MediaEncodeRequests.EncodeMetadata) != 0)
			{
				//取得中繼資料
				IMetadata metadata = this.EncodingMetadata;
				if (metadata == null && this.EncodingImage != null)
					metadata = this.EncodingImage.Metadata;

				//編碼中繼資料
				if (metadata != null)
				{
					Stream stream;
					if (!this.GetMediaStorageStream(version, destination, 0, out stream))
						throw new MediaEncodeFailedException(MediaEncodeFailedReason.CannotAccessStream);
					try
					{
						if (!this.EncodeMetadata(stream, metadata) || !this.CommitToMediaStorage(version, destination, 0, stream))
							return false;
					}
					finally
					{
						this.ReleaseMediaStorageStream(version, destination, stream);
					}
				}
			}

			//完成
			return true;
		}
		#endregion
		#region EncodeMetadata
		bool EncodeMetadata(Stream stream, IMetadata metadata)
		{
			//讀取原始 EXIF Tag
			long headerPosition = stream.Position;
			TiffReader tiffReader = new TiffReader(stream);
			ExifTagClassifier tagClassifier = new ExifTagClassifier(tiffReader.ReadAllTags());
			BitConverter bitConverter = tiffReader.BitConverter;

			//建立 EXIF Tag 容器
			Dictionary<ExifTagKey, ExifTag> allTags = new Dictionary<ExifTagKey, ExifTag>();

			//取得影像資料相關資訊
			this.PreparePrimaryIFD(bitConverter, tagClassifier.Ifd0, allTags);
			this.PreparePrimaryIFD(bitConverter, tagClassifier.Ifd1, allTags);

			//讀取影像資料
			byte[][] imageData = tiffReader.ReadImageData(Ifd.Ifd0);
			byte[][] thumbnailData = tiffReader.ReadImageData(Ifd.Ifd1);

			//轉換成 TIFF 中繼資料
			if (!(metadata is TiffMetadata))
				metadata = new TiffMetadata(metadata, true);

			//分類中繼資料項目
			List<ExifTag> tags = new List<ExifTag>();
			List<XmpProperty> xmpProperties = new List<XmpProperty>();
			foreach (ExifTag tag in (metadata as IExifMetadata).ExifTags)
			{
				if (!allTags.ContainsKey(tag.Key))
					allTags[tag.Key] = tag;
			}
			xmpProperties.AddRange((metadata as IXmpMetadata).XmpProperties);

			//建立 XMP 文件
			ExifTagKey xmpTagKey = new ExifTagKey(Ifd.Ifd0, 700);
			allTags.Remove(xmpTagKey);
			if (xmpProperties.Count > 0)
			{
				byte[] xmpDocument = (new XmpWriter()).Write(xmpProperties);
				allTags[xmpTagKey] = new ExifTag(Ifd.Ifd0, 700, ExifTagType.Byte, (uint)xmpDocument.Length, xmpDocument);
			}

			//重新寫入 TIFF
			stream.Position = headerPosition;
			stream.SetLength(headerPosition);
			TiffWriter tiffWriter = new TiffWriter(stream, null, bitConverter);
			if (imageData != null)
				tiffWriter.WriteImageData(Ifd.Ifd0, imageData);
			if (thumbnailData != null)
				tiffWriter.WriteImageData(Ifd.Ifd1, thumbnailData);
			foreach (ExifTag tag in allTags.Values)
				tiffWriter.WriteTag(tag);
			tiffWriter.Commit();
			
			//完成
			return true;
		}
		#endregion
		#region PreparePrimaryIFD
		void PreparePrimaryIFD(BitConverter converter, IEnumerable<ExifTag> originalTags, Dictionary<ExifTagKey, ExifTag> allTags)
		{
			//取得影像資料相關資訊
			foreach (ExifTag tag in originalTags)
			{
				switch (tag.ID)
				{
					case 273:		//StripOffsets
					case 279:		//StripByteCounts
						break;
					case 256:		//ImageWidth
					case 257:		//ImageHeight
					case 258:		//BitsPerSample
					case 259:		//Compression
					case 262:		//PhotometricInterpretation
					case 277:		//SamplesPerPixel
					case 284:		//PlanarConfiguration
					case 530:		//YCbCrSubSampling
					case 531:		//YCbCrPositioning
					case 282:		//XResolution
					case 283:		//YResolution
					case 296:		//ResolutionUnit
					case 278:		//RowsPerStrip
					case 301:		//TransferFunction
					case 317:		//Predictor
					case 318:		//WhitePoint
					case 319:		//PrimaryChromaticities
					case 529:		//YCbCrCoefficients
					case 532:		//ReferenceBlackWhite
						allTags[tag.Key] = tag;
						break;
				}
			}
		}
		#endregion
	}

	/// <summary>
	/// TIFF 影像編碼工作工廠。
	/// </summary>
	public class TiffImageEncodeTaskFactory : ImageEncodeTaskFactory
	{
		#region 建構子
		/// <summary>
		/// 初始化新的 TiffImageEncodeTaskFactory 執行個體。
		/// </summary>
		public TiffImageEncodeTaskFactory()
			: base("TIFF Image EncodeTask Factory", ImageFormats.Tiff)
		{ }
		#endregion
		#region CreateEncodeTask
		/// <summary>
		/// 建立影像編碼工作。
		/// </summary>
		/// <param name="destination">儲存編碼結果的媒體儲存。</param>
		/// <returns>影像編碼工作。</returns>
		public override MediaEncodeTask CreateEncodeTask(MediaStorage destination)
		{
			return new TiffImageEncodeTask(destination);
		}
		#endregion
	}
}
